using System;
using System.Reflection;
using System.Threading;
using OptionParser;
using NBody;
using PGPlot;

[assembly: AssemblyTitle ("NBodyAnimate")]
[assembly: AssemblyVersion ("1.0.*")]
[assembly: AssemblyDescription ("Plot N-body systems using PGPlot.")]
[assembly: AssemblyCopyright ("2005 Joseph D. MacMillan")]

public class AnimateOptions : GetOptions
{
    [Option ("-d", "The PGPlot device.")]
    public string Device;
    
    [Option ("-xbox", "X-axis box options.")]
    public string XBoxOpts;
    
    [Option ("-ybox", "Y-axis box options.")]
    public string YBoxOpts;
    
    [Option ("-xlabel", "X-axis label.")]
    public string XLabel;
    
    [Option ("-ylabel", "Y-axis label.")]
    public string YLabel;
    
    [Option ("-axis", "Axis to look down")]
    public NBodyAnimate.Axis Axis;
    
    [Option ("-c", "Use comoving units")]
    public bool Comoving;
    
    [Option ("-s", "Sleep time between frames")]
    public double Sleep;
    
    [Option ("-l", "Limits (a string of 4 numbers)")]
    public string Limits;
    
    [Option ("-r", "Radius to plot (limits will be -r r -r r)")]
    public double Radius;
    
    [Option ("-n", "Number of points to plot")]
    public int NumToPlot;
    
    public AnimateOptions()
    {
        Device = "/xs";
        XBoxOpts = "";
        YBoxOpts = "";
        XLabel = "";
        YLabel = "";
        Axis = NBodyAnimate.Axis.Z;
        Sleep = 0.0;
        Limits = "";
        NumToPlot = 0;
        Radius = 0.0;
    }
}

public class NBodyAnimate
{
    public enum Axis { X, Y, Z };
    
    public static void Main(string[] args)
    {
        AnimateOptions opts = new AnimateOptions();
        opts.ProcessArgs(args);
        
        string[] files = opts.RemainingArgs;
        if (files.Length < 1)
        {
        	Console.Error.WriteLine("Please specify at least one N-body system file");
        	Environment.Exit(1);
        }
        
        // get limits and data from first file
        NBodySystem s = NBodySystem.Read(files[0]);
        if (opts.NumToPlot == 0 || opts.NumToPlot > s.NumParts)
        	opts.NumToPlot = s.NumParts;
        // will really plot this many points ...
        double mod = (int)Math.Ceiling((double)s.NumParts / (double)opts.NumToPlot);
        opts.NumToPlot = (int)Math.Ceiling((double)s.NumParts / (double)mod);
      	
       	double ti = s.Time;
        
        double[] xcol = new double[opts.NumToPlot];
        double[] ycol = new double[opts.NumToPlot];
        SetColumns(ref xcol, ref ycol, s, opts.NumToPlot, opts.Axis, opts.Comoving, ti);
        
        double x1, x2, y1, y2;
        if (opts.Radius > 0.0)
            opts.Limits = String.Format("-{0} {0} -{0} {0}", opts.Radius);
        if (opts.Limits == "")
        	SetLimits(xcol, ycol, out x1, out x2, out y1, out y2);
        else
        	ParseLimits(opts.Limits, out x1, out x2, out y1, out y2);
        
        // now it's time to plot
        Plot plot = new Plot(opts.Device);
        
        PlotPoints(plot, xcol, ycol, x1, x2, y1, y2, s.Time);
        
        for (int i = 1; i < files.Length; i++)
        {
        	s = NBodySystem.Read(files[i]);
        	SetColumns(ref xcol, ref ycol, s, opts.NumToPlot, opts.Axis, opts.Comoving, ti);
        	Thread.Sleep((int)(opts.Sleep * 1000.0));
        	PlotPoints(plot, xcol, ycol, x1, x2, y1, y2, s.Time);
        }
    }
    
    private static void SetColumns(ref double[] xcol, ref double[] ycol, NBodySystem s, int n, Axis axis, bool comoving, double ti)
    {
        int mod_value = 1;
        if (n < s.NumParts)
            mod_value = (int)Math.Ceiling((double)s.NumParts / (double)n);
        
        int px, py;
        if (axis == Axis.X)
        {
            px = 1;
            py = 2;
        }
        else if (axis == Axis.Y)
        {
            px = 2;
            py = 0;
        }
        else // if (axis == Axis.Z)
        {
            px = 0;
            py = 1;
        }
        
        double scale = 1.0f;
        if (comoving)
        	scale = (double)(1.0 / Math.Pow(s.Time / ti, 2.0 / 3.0));
        
        int c = 0;
        for (int i = 0; i < s.NumParts; i++)
        {
        	if ((i % mod_value) == 0)
        	{
        		xcol[c] = s[i].Position(px) * scale;
        		ycol[c] = s[i].Position(py) * scale;
        		c++;
        	}
        }
    }
    
    private static void SetLimits(double[] xcol, double[] ycol, out double x1, out double x2, out double y1, out double y2)
    {
    	double small_x = xcol[0];
    	double large_x = xcol[0];
    	double small_y = ycol[0];
    	double large_y = ycol[0];
    	
    	for (int i = 1; i < xcol.Length; i++)
    	{
    		small_x = (xcol[i] < small_x) ? xcol[i] : small_x;
        	large_x = (xcol[i] > large_x) ? xcol[i] : large_x;
        	small_y = (ycol[i] < small_y) ? ycol[i] : small_y;
        	large_y = (ycol[i] > large_y) ? ycol[i] : large_y;
        }
        
        x1 = small_x - 0.1f * (large_x - small_x);
        x2 = large_x + 0.1f * (large_x - small_x);
        y1 = small_y - 0.1f * (large_y - small_y);
        y2 = large_y + 0.1f * (large_y - small_y);
    }
    
    private static void ParseLimits(string limits, out double x1, out double x2, out double y1, out double y2)
    {
    	string[] num = limits.Split(null);
    	if (num.Length != 4)
    	{
    		Console.Error.WriteLine("Error: Limits must be given as a string of 4 numbers.");
    		Console.Error.WriteLine("Using default limits (0, 1, 0, 1)");
    		x1 = y1 = 0.0f;
    		x2 = y2 = 1.0f;
    	}
    	else
    	{
    		x1 = Single.Parse(num[0]);
    		x2 = Single.Parse(num[1]);
    		y1 = Single.Parse(num[2]);
    		y2 = Single.Parse(num[3]);
    	}
    }
    
    private static void PlotPoints(Plot plot, double[] xcol, double[] ycol, double x1, double x2, double y1, double y2, double time)
    {
    	string ct = String.Format("{0:f2}", time);
    	
    	plot.BufferBegin();	
    	plot.Erase();
        plot.SetViewport(0.0, 1.0, 0.0, 1.0);  
        plot.SetLimits(x1, x2, y1, y2, true);
        plot.Points(xcol, ycol, -1);
        plot.MText("B", -1.0, 0.0, 0.0, ct);
        plot.BufferEnd();
    }    	
}
